﻿/*!
 * GreenIsomer Open DataGrid Widget v1.0.5
 * Copyright 2014 Michael W. Jackson
 * Released under the MIT license
 */

define(['jquery', 'knockout'], function($, ko) {
	if (!$) { throw new Error("GreenIsomer Open DataGrid Widget requires jQuery."); }
	if (!ko) { throw new Error("GreenIsomer Open DataGrid Widget requires Knockout"); }

	var grid = function() {

		// Set Global Info and Settings

		this.version = '1.0.5';

		this.defaults = {
			sortHeaders: {
				selector: '*[data-sort]',
				toolTip: 'Click to Sort',
				defaultSort: null,
				status: {
					className: null,
					ascending: {
						icon: null,
						text: '↑',
						toolTip: 'Sorted Ascending'
					},
					descending: {
						icon: null,
						text: '↓',
						toolTip: 'Sorted Descending'
					}
				}
			},
			pagination: {
				pageSize: 20,
				selector: 'ul.pagination',
				displayCount: 9,
				previous: {
					show: true,
					icon: null,
					text: '←',
					toolTip: 'Go to Previous Page'
				},
				next: {
					show: true,
					icon: null,
					text: '→',
					toolTip: 'Go to Next Page'
				}
			}
		};

		// Create initialization method

		this.load = function(selector, apiUrl, options) {
			var $this = $(selector);

			var settings = $.extend(true, {}, this.defaults, options);

			// Initialize pagination

			if (settings.pagination.selector) {
				var $pagination = $this.find(settings.pagination.selector).attr('data-bind', 'visible: pageCount() > 1').empty();
				var addPreviousNext = function(prevNextSettings, listItemBind, anchorBind) {
					if (!prevNextSettings.show) return;
					var $listItem = $('<li "></li>').attr('data-bind', listItemBind).attr('title', prevNextSettings.toolTip);
					var $anchor = $('<a href="#"></a>').attr('data-bind', anchorBind);
					if (prevNextSettings.text) $anchor.text(prevNextSettings.text);
					if (prevNextSettings.icon) $anchor.prepend($('<span></span>').addClass(prevNextSettings.icon));
					$anchor.appendTo($listItem);
					$listItem.appendTo($pagination);
				};
				addPreviousNext(settings.pagination.previous, "css: currentPage() <= 1 ? 'disabled' : ''", "click: function() { if(currentPage() > 1) previousPage(); return false; }");
				$pagination.append('<!-- ko foreach: pages -->');
				$pagination.append('<li data-bind="css: active() ? \'active\' : \'\'"><a href="#" data-bind="text: text, value: number, click: function() { active(true); return false; }"></a></li>');
				$pagination.append('<!-- /ko -->');
				addPreviousNext(settings.pagination.next, "css: currentPage() >= pageCount() ? 'disabled' : ''", "click: function() { if(currentPage() < pageCount()) nextPage(); return false; }");
			}

			// Helper methods

			var javaScriptEncode = function(text) {
				if (!text) return '';
				return text.replace(/[\\]/g, '\\\\').replace(/[']/g, '\\\'');
			};

			// Initialize sort headers

			if (settings.sortHeaders.selector) {
				$this.find(settings.sortHeaders.selector).each(function() {
					var sortField = $(this).attr('data-sort');
					if (!sortField) return;

					// Add bound anchor tag within element

					var $anchor = $('<a href="#"></a>');
					if (settings.sortHeaders.toolTip) $anchor.attr('title', settings.sortHeaders.toolTip);
					$anchor.attr('data-bind', "click: function () { sort('" + javaScriptEncode(sortField) + "'); return false; }");
					$(this).wrapInner($anchor);

					// Add trailing status text/icon after existing text/content

					var ascendingSettings = settings.sortHeaders.status.ascending;
					var descendingSettings = settings.sortHeaders.status.descending;
					if (settings.sortHeaders.status.className || ascendingSettings.text || descendingSettings.text
						|| ascendingSettings.icon || descendingSettings.icon || ascendingSettings.toolTip || descendingSettings.toolTip) {

						var $status = $('<span></span>');
						if (settings.sortHeaders.status.className) $status.addClass(settings.sortHeaders.status.className);

						var statusBind = 'visible: currentSort() === "' + javaScriptEncode(sortField) + '"';
						if (ascendingSettings.text || descendingSettings.text) statusBind += ', text: currentDescending() ? "' + javaScriptEncode(descendingSettings.text) + '" : "' + javaScriptEncode(ascendingSettings.text) + '"';
						if (ascendingSettings.icon || descendingSettings.icon) statusBind += ', css: currentDescending() ? "' + javaScriptEncode(descendingSettings.icon) + '" : "' + javaScriptEncode(ascendingSettings.icon) + '"';
						if (ascendingSettings.toolTip || descendingSettings.toolTip) statusBind += ', attr: {title: currentDescending() ? "' + javaScriptEncode(descendingSettings.toolTip) + '" : "' + javaScriptEncode(ascendingSettings.toolTip) + '"}';
						$status.attr('data-bind', statusBind);

						$(this).append($status);
						$status.before(' ');

					}

				});
			}

			// Initialize view model

			$this.each(function() {

				var viewModel = function() {
					var self = this;

					self.apiUrl = apiUrl;

					// Paging variables

					self.currentPage = ko.observable(1);
					self.pageSize = ko.observable(settings.pagination.pageSize);
					self.pageDisplayCount = ko.observable(settings.pagination.displayCount);
					self.firstPage = function() { self.currentPage(1); };
					self.nextPage = function() { self.currentPage(self.currentPage() + 1); };
					self.previousPage = function() { self.currentPage(self.currentPage() - 1); };
					self.lastPage = function() { self.currentPage(self.pageCount()); };
					self.pages = ko.observableArray();

					// Sorting variables

					self.currentSort = ko.observable(settings.sortHeaders.defaultSort);
					self.currentDescending = ko.observable(false);
					self.sort = function(field) {
						if (self.currentSort() === field) {
							if (self.currentDescending()) {
								self.currentSort('');
								self.currentDescending(false);
							} else self.currentDescending(true);
						} else {
							self.currentSort(field);
							self.currentDescending(false);
						}
					};

					// Data variables and derivatives

					self.response = ko.observable({});
					self.count = ko.observable(0);
					self.items = ko.observableArray();

					self.pageCount = ko.computed(function() {
						return Math.ceil(self.count() / self.pageSize());
					}, self);

					ko.computed(function() {
						self.count(self.response().count);
						var pageCount = self.pageCount();
						if (self.currentPage() > pageCount) self.currentPage(pageCount);
					}, self);
					ko.computed(function() {
						self.items(self.response().results);
					}, self);

					// Pages calculation

					ko.computed(function() {

						// Calculate ideal range

						var currentPage = self.currentPage();
						var pageCount = self.pageCount();
						var pageDisplayCount = self.pageDisplayCount();
						if (pageDisplayCount < 5) pageDisplayCount = 5;
						if (pageDisplayCount > pageCount) pageDisplayCount = pageCount;
						var pageStart = pageDisplayCount === pageCount ? 1 : currentPage - Math.floor((pageDisplayCount - 1) / 2);
						if (pageStart + pageDisplayCount - 1 > pageCount) pageStart -= ((pageStart + pageDisplayCount - 1) - pageCount);
						if (pageStart < 1) pageStart = 1;
						var pageEnd = pageStart + pageDisplayCount - 1;

						// Create pages, always showing first and last and replacing adjacent gaps with ellipses

						var pages = new Array();
						var p;
						if (pageStart > 1) {
							pageStart += 1;
							pages.push({ number: 1, text: '1' });
						}
						if (pageEnd < pageCount) pageEnd -= 1;
						for (p = pageStart; p <= pageEnd; p++) {
							pages.push({
								number: p,
								text: (p === pageStart || p === pageEnd) && !(p <= 2 || p >= pageCount - 1) ? '…' : p.toString()
							});
						}
						if (pageEnd < pageCount) pages.push({ number: pageCount, text: pageCount.toString() });

						// Build bind-able pages based above calculations

						var activePages = new Array();
						for (p in pages) {
							activePages.push({
								number: pages[p].number,
								text: pages[p].text,
								active: ko.computed({
									read: function() { return this.root.currentPage() === this.page.number; },
									write: function(value) { if (value) this.root.currentPage(this.page.number); },
									owner: { root: self, page: pages[p] }
								})
							});
						}
						self.pages(activePages);

					});

					// ODATA JSON REST API call

					self.refresh = function() {
						var currentPage = self.currentPage() - 1;
						var pageSize = self.pageSize();

						var args = new Array();
						args.push("$inlinecount=allpages");
						if (currentPage > 0) args.push('$skip=' + (pageSize * currentPage).toString());
						args.push('$top=' + pageSize.toString());
						var currentSort = self.currentSort();
						if (currentSort) args.push('$orderby=' + currentSort + (self.currentDescending() ? ' desc' : ''));

						$.getJSON(apiUrl + (args.length != 0 ? '?' + args.join('&') : ''), null, self.response);
					};

					ko.computed(function() {
						self.refresh();
					}, this).extend({ throttle: 10 });

				};

				// Bind view model

				ko.applyBindings(new viewModel(), this);

			});

		};

		this.refresh = function(selector) {
			$(selector).each(function() {
				ko.dataFor(this).refresh();
			});
		};
	};

	return new grid();
});